package org.gd.science.ulrmapper.factory;

import org.gd.science.ulrmapper.annotations.RootUrl;
import org.gd.science.ulrmapper.annotations.UrlParameter;

import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class UrlParserFactory {

    public static final String DEFAULT_NAME_VALUE = "##default";
    public static final String DEFAULT_VALUE = "";
    private static final String AND = "&";
    private static final String START = "?";

    public static String parse(Object instance) throws UrlParserException {
        Class<?> clazz = instance.getClass();
        if(!clazz.isAnnotationPresent(RootUrl.class)) {
            throw new UrlParserException("not annotated instance: " + instance.toString());
        }
        RootUrl rootUrl = clazz.getAnnotation(RootUrl.class);
        StringBuilder url = new StringBuilder();
        if(rootUrl.required() && !isUrl(rootUrl.value())) {
            throw new UrlParserException("bad url: " + rootUrl.value());
        }
        if(isUrl(rootUrl.value())) {
            url.append(rootUrl.value());
        }
        AtomicInteger paramsCount = new AtomicInteger(0);
        List<String> parameterNames = new ArrayList<>();
        for(Field field: clazz.getDeclaredFields()) {
            if(!field.isAnnotationPresent(UrlParameter.class)) {
                continue;
            }
            UrlParameter urlParameter = field.getAnnotation(UrlParameter.class);
            String parameterName = urlParameter.value();
            String defaultValue = urlParameter.defaultValue();
            if(parameterName.equals(DEFAULT_NAME_VALUE)) {
                parameterName = field.getName();
            }
            if(defaultValue == null) {
                defaultValue = DEFAULT_VALUE;
            }
            if(parameterNames.contains(parameterName.toLowerCase())) {
                throw new UrlParserException("duplicate parameter name: \"" + parameterName + "\"");
            }
            parameterNames.add(parameterName.toLowerCase());
            String value = toString(getFieldValue(field, instance, defaultValue));
            if(urlParameter.required() || !isEmpty(value)) {
                url.append(paramsCount.getAndIncrement() == 0 ? START : AND)
                        .append(parameterName).append("=")
                        .append(value);
            }

        }
        return url.toString();
    }

    @SuppressWarnings("unused")
    @Deprecated
    private static Object getFieldValue(Field field, Object instance)
            throws UrlParserException {
        return getFieldValue(field, instance, "");
    }

    private static Object getFieldValue(Field field, Object instance, String defaultValue)
            throws UrlParserException {
        try {
            Object value;
            if(field.isAccessible()) {
                value = field.get(instance);
                return value == null ? defaultValue : value;
            }
            field.setAccessible(true);
            value = field.get(instance);
            field.setAccessible(false);
            return value == null ? defaultValue : value;
        } catch (IllegalAccessException e) {
            throw new UrlParserException(e);
        }
    }

    private static boolean isUrl(String url) {
        try {
            new URL(url);
            return true;
        } catch (MalformedURLException | RuntimeException ignore) {}
        return false;
    }

    private static String toString(Object object) {
        if(object == null) {
            return "";
        }
        return object.toString();
    }

    private static boolean isEmpty(String string) {
        return string == null || string.isEmpty();
    }
}
